/*
 * unittrace.h
 *
 * Created: 29.10.2018 20:36:59
 *  Author: Dennis aka nqtronix (github.com/nqtronix)
 *
 * Description:
 * Testing is an important part of writing code, especially if the code is meant to be re-used for
 * other projects. On small MCUs testing can be quite tricky as the memory is limited and errors
 * can't be easily reported with `printf()` or similiar.
 *
 * unittrace provides basic testing functionality and is designed with the limits of MCUs in mind.
 * It runs directly on the hardware and thus can catch errors other test software can't. Fetch the
 * results through a debugger or with a function.
 *
 * See included 'README.md' for additional information.
 */


#ifndef UNITTRACE_H_
#define UNITTRACE_H_


#include <stdint.h>		// required for data types (uint8_t, uint16_t, ...)
#include "utility/macros/com/macro_type.h"


//////////////////////////////////////////////////////////////////////////
// Check requirements
//////////////////////////////////////////////////////////////////////////

#ifndef __GNUC__
	#error unittrace.h requires "always inline functions", "local labels" and "typeof" offered by a GNU C/ GCC compiler!
#endif
// Workarounds are possible. See comments in the source code.


//////////////////////////////////////////////////////////////////////////
// General Info
//////////////////////////////////////////////////////////////////////////

// version numbering is based on "Semantic Versioning 2.0.0" (semver.org)
#define UNITTRACE_VERSION_MAJOR		0
#define UNITTRACE_VERSION_MINOR		1
#define UNITTRACE_VERSION_PATCH		0
#define UNITTRACE_VERSION_SUFFIX	
#define UNITTRACE_VERSION_META

// For all development versions (0.x.x) the patch version is increased whenever a function was renamed


//////////////////////////////////////////////////////////////////////////
// Settings (This section can be modified by the user)
//////////////////////////////////////////////////////////////////////////

// Define the amount of failed events to log in detail. The same event can be logged multiple times.
// Each event required 4 byte of RAM. There is no upper limit except the device's SRAM. The compiler
// will automatically choose the  smallest possible data type for most efficient operation.
#define UNITTRACE_LIST_SIZE		8

// Especially on small embedded systems it is best to choose the smallest data type for every
// variable. This macro selects the smallest possible type based on 'UNITTRACE_LIST_SIZE'. Change
// to 'uint8_t', 'uint16_t' or 'uint32_t' to manually overwrite this feature.
//
// extra '#define UNITTRACE_COUNTER_TYPE' prevents VAssistX from marking 'ut_cnt_t' red
#define UNITTRACE_COUNTER_TYPE _type_min(UNITTRACE_LIST_SIZE)
typedef UNITTRACE_COUNTER_TYPE ut_cnt_t;

// If 'UNITTRACE_USE_EXT_COUNTER' is defined, failed asserts will be counted till the maximum value,
// even if no space in the array is available. This might be helpful for some test cases, but does
// increase the assert execution time slightly.
#define UNITTRACE_USE_EXT_COUNTER


//////////////////////////////////////////////////////////////////////////
// Global Variables
//////////////////////////////////////////////////////////////////////////

// I couldn't figure out how to pass a void array half-way decently, so both variables are
// now global instead. This is not "how it's don", I know, but unittrace is for testing only anyway.

// WATCH THESE VARIABLES IN THE DEBUGGER

// contains all traced events
volatile void* unittrace_array[UNITTRACE_LIST_SIZE];

// amount of traced events
ut_cnt_t unittrace_count;


//////////////////////////////////////////////////////////////////////////
// Function Declarations
//////////////////////////////////////////////////////////////////////////

// 'ut_assert' is used to validate any given condition. If the condition is false (cond == 0), the
// assembler instruction address is stored to an array (unless it is full). This array can be read
// during debugging.
// If your compiler does not support inline functions, use the macro 'UT_ASSERT(cond)'.
static inline void ut_assert(uint8_t cond) __attribute__((always_inline));

// If you manually want to write a value to the array, you can use this macro. Usually the first few
// instructions are generated by the compiler and their addresses will never naturally appear in
// said array. Thus you may abuse low values for your own, custom purpose (<50 should be safe on
// AVR8 MCUs)
void ut_assert_manual(void* addr, uint8_t cond);


//////////////////////////////////////////////////////////////////////////
// Macros
//////////////////////////////////////////////////////////////////////////

// alternative implementation of the macro 'ut_assert(cond)'
#define UT_ASSERT(cond)		do{__label__ lcl; lcl: ut_assert_manual(&&lcl, cond);}while(0)

// places a single 'nop' instruction. This will not be optimized an is ideal to place a breakpoint.
// Unlike an inline function a macro is taken "as is". In this case it make debugging a little
// easier, so support for the equivalent inline function has been removed.
#define UT_BREAK()			asm("nop")


//////////////////////////////////////////////////////////////////////////
// Inline functions
//////////////////////////////////////////////////////////////////////////

// Inline functions MUST be defined in the .h, not in the .c file to work correctly!

// This function MUST be always inline to create an individual label for each call. In GCC inline
// functions declared with '__attribute__((always_inline))' are as fast as the respective macro.
// '__label__' creates a local label to prevent a littered namespace. Alternatively you could use
// the build-in macro '__COUNTER__' and concat '##' to generate unique labels.
static inline void ut_assert(uint8_t cond)
{
	__label__ local_label;
	local_label:
	ut_assert_manual(&&local_label, cond);
}

#endif /* UNITTRACE_H_ */